﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

using System.IO;
using System.Diagnostics;
using System.Windows;

namespace SteamMoverWPF
{
    class FileMoverQueue : IDisposable
    {
        ReaderWriterLockSlim _lock;
        Queue<FileMove> _q ;
        bool isExecuting;
        bool stopRequested;

        public FileMoverQueue()
        {
            _q = new Queue<FileMove>();
            _lock = new ReaderWriterLockSlim();
        }

        public void Dispose()
        {
            _lock.Dispose();
            _q.Clear();
            _q = null;
        }

        private void Add(FileMove m)
        {
            _lock.EnterWriteLock();
            try
            {
                _q.Enqueue(m);
            }
            finally
            {
                _lock.ExitWriteLock();
            }
        }

        private FileMove Next()
        {
            _lock.EnterReadLock();
            try
            {
                if (_q.Count > 0)
                    return _q.Dequeue();
                else
                    return null;
            }
            finally
            {
                _lock.ExitReadLock();
            }
        }

        public void Add(Game game, String newLocation)
        {
            game.IsQueued = true;
            Add(new FileMove() { SelectedGame = game, newLocation = newLocation });
        }

        //TODO: review this for possible concurrency issues
        public void Stop()
        {
            stopRequested = true;
        }

        /// <summary>
        /// Async task which cycles through the queue until empty.
        /// </summary>
        /// <returns></returns>
        public async Task ExecuteQueue()
        {
            if (isExecuting)
                return;

            isExecuting = true;

            FileMove m = Next();
            while (m != null)
            {
                await Task.Run(() => PerformMove(m));
                m.SelectedGame.IsQueued = false;

                if (stopRequested)
                {
                    stopRequested = false;
                    break;
                }

                m = Next();
            }

            isExecuting = false;
        }

        /// <summary>
        /// Builds a batch file to perform the required operations using MKLINK, RMDIR and Robocopy
        /// Should be made obsolete in 2.0 release when copy and junction creation methods are handled internally
        /// </summary>
        /// <param name="m"></param>
        /// <returns></returns>
        private void PerformMove(FileMove m)
        {
            m.SelectedGame.IsBeingMoved = true;

            // move files from MainLocation to AlternateLocation
            if (!m.SelectedGame.IsJunction)
            {
                if (CopyFolder(m.SelectedGame.MainLocation, new DirectoryInfo(m.newLocation)))
                {
                    DeleteFolder(m.SelectedGame.MainLocation.FullName);
                    // create new Junction at MainLoation pointing to AlternateLocation
                    CreateJunction(
                        m.SelectedGame.MainLocation.Parent, 
                        m.SelectedGame.Name, 
                        new DirectoryInfo(m.newLocation));
                }
            }

            // move from AlternateLocation back to MainLocation
            else if (m.newLocation == m.SelectedGame.MainLocation.FullName)
            {
                DirectoryInfo altloc = m.SelectedGame.AlternateLocation;
                // delete existing Junction at MainLocation
                DeleteJunction(m.SelectedGame.MainLocation.FullName);
                // move files from AlternateLocation to MainLocation
                if (CopyFolder(altloc, new DirectoryInfo(m.newLocation)))
                {
                    DeleteFolder(altloc.FullName);
                }
                else
                {
                    // if copy didn't work, return to previous state
                    CreateJunction(
                        m.SelectedGame.MainLocation.Parent,
                        m.SelectedGame.Name,
                        new DirectoryInfo(m.newLocation));
                }
            }

            // move from AlternateLocation to new AlternateLocation
            else
            {
                DirectoryInfo altloc = m.SelectedGame.AlternateLocation;
                // delete existing Junction at MainLocation
                DeleteJunction(m.SelectedGame.MainLocation.FullName);
                // move files from AlternateLocation1 to AlternateLocation2
                if (CopyFolder(altloc, new DirectoryInfo(m.newLocation)))
                {
                    DeleteFolder(altloc.FullName);
                    // create new Junction at MainLocation pointing to AlternateLocation2
                    CreateJunction(
                        m.SelectedGame.MainLocation.Parent, 
                        m.SelectedGame.Name,
                        new DirectoryInfo(m.newLocation));
                }
                else
                {
                    // restore old state
                    CreateJunction(
                        m.SelectedGame.MainLocation.Parent,
                        m.SelectedGame.Name,
                        new DirectoryInfo(m.newLocation));
                }
            }
            m.SelectedGame.IsBeingMoved = false;
        }

        // TODO: could make with JunctionPoint library... maybe in 2.0
        private void CreateJunction(DirectoryInfo JunctionLocation, String JunctionName, DirectoryInfo JunctionDestination)
        {
            String Junction = System.IO.Path.Combine(JunctionLocation.FullName, JunctionName);
            JunctionPoint.Create(Junction, JunctionDestination.ToString(), true);
        }

        // TODO: do in .NET code in v2.0
        private void DeleteJunction(string junctionName)
        {
            Directory.Delete(junctionName);
        }

        private void DeleteFolder(string junctionName)
        {
            Directory.Delete(junctionName, true);
        }

        // TODO: do in .NET code in v2.0
        //public String MoveFolder(DirectoryInfo SourceFolder, DirectoryInfo DestinationFolder)
        //{
        //    if (SourceFolder == null || DestinationFolder == null)
        //        throw new ArgumentException();

        //    String newDestination = System.IO.Path.Combine(DestinationFolder.FullName, SourceFolder.Name);
        //    return String.Format(@"ROBOCOPY ""{0}"" ""{1}"" /MOVE /E", SourceFolder.FullName, newDestination);
        //}

        private bool CopyFolder(DirectoryInfo SourceFolder, DirectoryInfo DestinationFolder)
        {
            try
            {
                if (SourceFolder == null || DestinationFolder == null)
                    throw new ArgumentException();

                if (!DestinationFolder.Exists)
                    DestinationFolder.Create();

                foreach (FileSystemInfo entry in SourceFolder.GetFileSystemInfos())
                {
                    // is a directory?
                    if (entry.Attributes.HasFlag(FileAttributes.Directory))
                    {
                        // copy dir, bail if an operation failed
                        if (!CopyFolder(
                            (DirectoryInfo)entry,
                            new DirectoryInfo(
                                System.IO.Path.Combine(
                                    DestinationFolder.FullName,
                                    entry.Name))))
                        {
                            return false;
                        }
                    }

                    Debug.WriteLine(entry.Attributes.ToString());
                    if (!entry.Attributes.HasFlag(FileAttributes.Directory))
                        ((FileInfo)entry).CopyTo(System.IO.Path.Combine(DestinationFolder.FullName, entry.Name), true);
                }
            }
            catch (Exception ex)
            {
                DestinationFolder.Delete(true);
                // not sure if having a mbox here is kosher
                MessageBoxResult mboxresult =
                    MessageBox.Show(
                        "There was an error while copying files:\n\n" + ex.ToString(),
                        ex.GetType().ToString(),
                        MessageBoxButton.OK,
                        MessageBoxImage.Warning,
                        MessageBoxResult.OK);
                return false;
            }

            return true;
        }
    }

    // TODO: put more logic in here instead of in queue object
    class FileMove
    {
        public Game SelectedGame { get; set; }
        public String newLocation { get; set; }
    }
}
